kanten = zylinder.edges()
for k in kanten:
print(k.geom_type) # CIRCLE oder LINE oder ...
print(k.length)
wire = zylinder.faces()[0].wires()[0]
print(len(wire.edges()), "Kanten im Wire")
→ Flächen können mehrere Wires haben: äußere Kontur + innere Löcher
flaechen = zylinder.faces()
for f in flaechen:
print(f.geom_type) # PLANE oder CYLINDER oder ...
print(f.area)
shell = zylinder.shells()[0]
print(len(shell.faces()), "Flächen in der Shell")
→ Eine geschlossene Shell begrenzt ein Volumen
solid = zylinder.solids()[0]
print(solid.volume) # Volumen in mm³
print(solid.area) # Oberfläche in mm²
Part, Sketch, Curve in build123d sind spezialisierte Compoundsquader = bd.Box(30, 20, 10)
gruppe = bd.Compound([zylinder, quader])
print(len(gruppe.solids()), "Solide in der Gruppe")
| Typ | Dimension | Begrenzt durch | Geometrie |
|---|---|---|---|
| Vertex | 0D | – | Punkt |
| Edge | 1D | Vertices | Kurve |
| Wire | 1D | Edges | – |
| Face | 2D | Wires | Fläche |
| Shell | 2D | Faces | – |
| Solid | 3D | Shells | – |
| Compound | – | beliebig | – |
Die Elemente bilden eine hierarchische Struktur (gerichteter Graph):
Solid
└── Shell
├── Face
│ └── Wire
│ └── Edge
│ └── Vertex
└── Face
└── Wire
└── Edge
└── Vertex ← derselbe Vertex wie oben!
Entscheidend: Teilelemente werden geteilt, nicht kopiert.
Zwei Objekte sind verbunden, wenn sie ein gemeinsames Begrenzungselement teilen:
quader = bd.Box(20, 20, 10)
# Welche Flächen teilen eine bestimmte Kante?
kante = quader.edges()[0]
for f in quader.faces():
if kante in f.edges():
print("Fläche enthält diese Kante:", f.center())
quader = bd.Box(20, 20, 10)
# Alle Kanten einer bestimmten Fläche
oberseite = quader.faces().sort_by(bd.Axis.Z).last
print("Kanten der Oberseite:", len(oberseite.edges()))
# Alle Vertices einer bestimmten Kante
kante = oberseite.edges()[0]
print("Vertices der Kante:", len(kante.vertices()))
→ Die Topologie erlaubt Navigation von oben nach unten durch die Hierarchie
# Quader nach boolescher Operation
quader = bd.Box(40, 40, 20)
loch = bd.Cylinder(radius=8, height=20)
ergebnis = quader - loch
print("Faces: ", len(ergebnis.faces())) # 7
print("Edges: ", len(ergebnis.edges())) # 15
print("Vertices:", len(ergebnis.vertices())) # 10
Boolesche Operationen verändern die Topologie – neue Elemente entstehen, alte entfallen.
Gedankenexperiment: Sechs quadratische Flächen, topologisch zu einer Shell verbunden.
Was entsteht?
Die Topologie ist identisch – 6 Faces, 12 Edges, 8 Vertices, alles verbunden.
→ Wir brauchen eine zusätzliche Information: in welche Richtung zeigt jede Fläche?
Die Lösung: Jede Face trägt eine Richtungsinformation – den Normalenvektor.
Regel: In einem gültigen Solid zeigt die Normale immer vom Material weg (nach außen).
Dieselbe Logik eine Ebene tiefer: Welche Seite einer Fläche ist „innen"?
Regel: Wenn man von außen auf eine Face schaut (entgegen der Normalen), liegt das Material links von der Durchlaufrichtung jeder Kante.
# build123d prüft automatisch die Orientierung
ergebnis = quader - loch
print(ergebnis.is_valid) # True wenn korrekt
→ build123d und OCCT kümmern sich meist automatisch darum – aber das Konzept erklärt, warum manche Operationen fehlschlagen.
Die Topologie-Konzepte dieser Vorlesung sind nicht an eine Software gebunden:
| Software | Kernel | Standard |
|---|---|---|
| CATIA | CGM (Dassault) | B-Rep |
| SolidWorks | Parasolid (Siemens) | B-Rep |
| NX (Unigraphics) | Parasolid (Siemens) | B-Rep |
| FreeCAD | OCCT | B-Rep |
| build123d | OCCT | B-Rep |
→ Vertex, Edge, Wire, Face, Shell, Solid – überall dieselben Konzepte, nur unterschiedliche Syntax.
ISO 10303 (STEP) ist das universelle Austauschformat für B-Rep-Geometrie.
import build123d as bd
# Eine STEP-Datei aus CATIA laden:
fremdes_teil = bd.import_step("catia_teil.step")
# Und sofort mit denselben Methoden arbeiten:
print(len(fremdes_teil.faces())) # Flächen zählen
print(len(fremdes_teil.edges())) # Kanten zählen
fremdes_teil.show_topology() # Struktur anzeigen
Das Gleiche (normiert durch B-Rep / ISO 10303):
Das Unterschiedliche (kernel-spezifisch):
→ Wer B-Rep versteht, kann sich in jedem professionellen CAD-System orientieren.
Jedes Objekt stellt Selektoren bereit, die ShapeList zurückgeben:
part = bd.Box(40, 30, 20) - bd.Cylinder(radius=8, height=20)
part.vertices() # alle Vertices
part.edges() # alle Kanten
part.wires() # alle Wires
part.faces() # alle Flächen
part.solids() # alle Körper
ShapeList ist eine Liste mit Zusatzmethoden zum Filtern und Sortieren.
part = bd.Box(40, 30, 20)
# Nach Position entlang einer Achse
oberseite = part.faces().sort_by(bd.Axis.Z).last # höchste Fläche
unterseite = part.faces().sort_by(bd.Axis.Z).first # tiefste Fläche
# Nach Geometrieeigenschaft
groesste = part.faces().sort_by(bd.SortBy.AREA).last
laengste = part.edges().sort_by(bd.SortBy.LENGTH).last
# Gruppieren
gruppen = part.faces().group_by(bd.SortBy.AREA) # Liste von Listen
from build123d import GeomType
part = bd.Box(30, 20, 10) - bd.Cylinder(radius=5, height=10)
# Nach Achsausrichtung (Kanten parallel, Flächen normal)
part.edges().filter_by(bd.Axis.Z) # vertikale Kanten
part.faces().filter_by(bd.Axis.Z) # Ober-/Unterseite
# Nach Geometrietyp
part.edges().filter_by(GeomType.CIRCLE) # Kreiskanten
part.faces().filter_by(GeomType.PLANE) # ebene Flächen
part.faces().filter_by(GeomType.CYLINDER) # zylindrische Flächen
# Nach Position (Mittelpunkt der Kante/Fläche)
part.edges().filter_by_position(bd.Axis.Z, 4, 6)
Problem: Nach jeder Operation kann sich die interne Nummerierung der Objekte ändern!
part_v1 = bd.Box(40, 30, 20)
# part_v1.faces()[0] ← Index 0 ist eine bestimmte Fläche
part_v2 = bd.fillet(part_v1.edges(), radius=2)
# part_v2.faces()[0] ← Index 0 kann jetzt eine ANDERE Fläche sein!
→ Deshalb: immer semantisch selektieren (sort_by, filter_by), nie hart auf Index verlassen.
| Topologie | Geometrie | |
|---|---|---|
| Beschreibt | Struktur, Verbindungen | Form, Position |
| Fragt | Was ist womit verbunden? | Wo liegt was? |
| Beispiel | 6 Flächen, 12 Kanten | Fläche liegt bei z=10 |
| Ändert sich bei | Bool. Operationen | Skalierung, Verschiebung |
Solid → Shell → Face → Wire → Edge → Vertex
3D 2D 2D 1D 1D 0D
part.faces().sort_by(Axis.Z).last # höchste Fläche
part.edges().filter_by(GeomType.CIRCLE) # Kreiskanten
part.faces().group_by(SortBy.AREA)[-1] # größte Flächen
part.edges().filter_by_position(Axis.Z, 4, 6) # Kanten in Bereich
→ Robuste Selektion durch semantische Kriterien statt Indizes
In der nächsten Einheit: Wie wird Form mathematisch beschrieben?